package ar.com.cubika.trainees.bean.transportes.vehiculos;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;

import ar.com.cubika.trainees.bean.transportes.actividades.Mantenimiento;
import ar.com.cubika.trainees.bean.transportes.actividades.PlanContinuacionDeViajeLargo;
import ar.com.cubika.trainees.bean.transportes.actividades.PlanDiario;
import ar.com.cubika.trainees.bean.transportes.actividades.PlanEnMantenimiento;
import ar.com.cubika.trainees.bean.transportes.actividades.PlanEnViaje;
import ar.com.cubika.trainees.bean.transportes.actividades.PlanSinActividad;
import ar.com.cubika.trainees.bean.transportes.actividades.Viaje;
import ar.com.cubika.trainees.collections.MethodEqualsObjectPredicate;
import ar.com.cubika.trainees.collections.ParametrizedMethodPredicate;
import ar.com.cubika.trainees.date.DateUtils;
import ar.com.cubika.trainees.exceptions.BusinessException;

/**
 * Representa los vehiculos que usa la empresa para hacer viajes.
 * 
 * @author Carlos
 */
public abstract class Vehiculo {

	private String patente;
	private List<Viaje> viajes;
	private Set<Mantenimiento> mantenimientos;

	/**
	 * Crea un nuevo vehiculo
	 */
	protected Vehiculo(String patente) {
		this.patente = patente;
	}

	/**
	 * La carga maxima que puede llevar el vehiculo en un viaje, en kilos
	 */
	public abstract long getCargaMaxima();

	/**
	 * El tiempo que le lleva al vehiculo hacer un determinado viaje, en horas
	 */
	public abstract double getTiempo(Viaje viaje);

	/**
	 * El consumo de combustible calculado para un viaje, medido en litros
	 */
	public abstract double getConsumoCombustible(Viaje viaje);

	/**
	 * Devuelve <code>true</code> si el vehiculo puede hacer un determinado
	 * viaje, dadas las restricciones de tiempo, carga y disponibilidad.
	 * 
	 * @param viaje
	 *            el viaje en analisis
	 */
	public boolean puedeHacer(Viaje viaje) {
		return (this.getCargaMaxima() >= viaje.getCarga())
				&& (this.getTiempo(viaje) <= viaje.getTiempoMaximo())
				&& this.tieneDisponibilidad(viaje);
	}

	/**
	 * Devuelve <code>true</code> si el vehiculo puede hacer un determinado
	 * mantenimiento.
	 * 
	 * @param viaje
	 *            el mantenimiento en analisis
	 */
	public boolean puedeHacer(Mantenimiento mant) {
		Iterator diasIt = mant.getPeriodo().diasIterator();
		while (diasIt.hasNext()) {
			Date dia = (Date) diasIt.next();
			if (!this.getPlan(dia).isLibre()) {
				return false;
			}
		}
		return true;
	}

	protected boolean tieneDisponibilidad(Viaje viaje) {
		PlanDiario planDelDia = this.getPlan(viaje.getFechaPartida());
		double tiempoViaje = this.getTiempo(viaje);
		if (tiempoViaje <= 12.0) {
			return planDelDia.getTiempoOcupado() + tiempoViaje <= 12;
		} else {
			return planDelDia.isLibre()
					&& this.getPlan(
							DateUtils.addDays(viaje.getFechaPartida(), 1))
							.isLibre();
		}
	}

	/**
	 * El plan del vehiculo para un dia.
	 */
	public PlanDiario getPlan(Date fecha) {
		PlanDiario result;
		Collection<Viaje> viajes;
		Viaje viajeLargoDeAyer;
		Mantenimiento mant;
		if (!(viajes = this.getViajes(fecha)).isEmpty()) {
			PlanEnViaje enViaje = new PlanEnViaje(this, fecha);
			enViaje.addViajes(viajes);
			result = enViaje;
		} else if ((mant = this.getMantenimiento(fecha)) != null) {
			PlanEnMantenimiento enMant = new PlanEnMantenimiento(this, fecha);
			enMant.setMantenimiento(mant);
			result = enMant;
		} else if ((viajeLargoDeAyer = this.getViajeLargo(DateUtils.addDays(
				fecha, -1))) != null) {
			PlanContinuacionDeViajeLargo contViaje = new PlanContinuacionDeViajeLargo(
					this, fecha);
			contViaje.setViaje(viajeLargoDeAyer);
			result = contViaje;
		} else {
			result = new PlanSinActividad(this, fecha);
		}
		return result;
	}

	/**
	 * El viaje largo (de mas de 12 horas) para una fecha, null si no hay
	 * ninguno.
	 * 
	 * @param date
	 *            la fecha en cuestion
	 */
	protected Viaje getViajeLargo(Date fecha) {
		Collection viajes = this.getViajes(fecha);
		if (viajes.size() == 1) {
			Viaje viaje = (Viaje) viajes.iterator().next();
			return viaje.getTiempoEstimado() >= 12.0 ? viaje : null;
		} else {
			return null;
		}
	}

	/**
	 * Los viajes del vehiculo para una fecha
	 * 
	 * @see Vehiculo#getPlan(Date)
	 */
	protected Collection<Viaje> getViajes(Date fecha) {
		Collection select = CollectionUtils.select(this.getViajes(),
				new MethodEqualsObjectPredicate(fecha, "getFechaPartida"));
		return (Collection<Viaje>) select;
	}

	protected Mantenimiento getMantenimiento(Date fecha) {
		return null;
		// (Mantenimiento) CollectionUtils.find(this.getMantenimientos(),
		// new ParametrizedMethodPredicate(fecha, "incluyeFecha"));
	}

	/**
	 * La patente que identifica al vehiculo
	 */
	public String getPatente() {
		return patente;
	}

	public void setPatente(String patente) {
		this.patente = patente;
	}

	/**
	 * Solo para ser usado dese Viaje, se le asigna un viaje al vehiculo.
	 * 
	 * @see Viaje#asignar(Vehiculo)
	 */
	public void addViaje(Viaje viaje) {
		this.viajes.add(viaje);
	}

	/**
	 * Solo para ser usado desde Viaje, se le quita el viaje que habia sido
	 * previamente asignado al vehiculo
	 * 
	 * @see Viaje#desasignar()
	 */
	public void removeViaje(Viaje viaje) {
		this.viajes.remove(viaje);
	}

	/**
	 * Agrega un mantenimiento al vehiculo.
	 * 
	 * @throws ar.com.cubika.trainees.exceptions.BusinessException
	 *             si el mantenimiento no es para el vehiculo.
	 */
	public synchronized void addMantenimiento(Mantenimiento mant) {
		if (!mant.getVehiculo().equals(this)) {
			throw new BusinessException("El mantenimiento " + mant
					+ " no es del vehiculo " + this);
		}
		this.mantenimientos.add(mant);
	}

	/**
	 * Quita un mantenimiento del vehiculo.
	 * 
	 * @throws ar.com.cubika.trainees.exceptions.BusinessException
	 *             si el mantenimiento no esta entre los del vehiculo.
	 */
	public synchronized void removeMantenimiento(Mantenimiento mant) {
		if (!this.mantenimientos.contains(mant)) {
			throw new BusinessException("El vehiculo " + this
					+ " no incluye al mantenimiento " + mant);
		}
		this.mantenimientos.remove(mant);
	}

	/**
	 * Los viajes que tiene asignado el vehiculo
	 */
	public Collection<Viaje> getViajes() {
		return new ArrayList<Viaje>(this.viajes);
	}

	public void setViajes(List<Viaje> viajes) {
		this.viajes = viajes;
	}

	/**
	 * Los mantenimientos que tiene planificado el vehiculo
	 */
	public Collection<Mantenimiento> getMantenimientos() {
		return new ArrayList<Mantenimiento>(this.mantenimientos);
	}

	/**
	 * Me limpian todos los viajes asignados - para test
	 */

	public String toString() {
		return this.getTipoVehiculo() + " " + this.getPatente();
	}

	public String getTipoVehiculo() {
		String name = this.getClass().getName();
		return name.substring(name.lastIndexOf(".") + 1).toLowerCase();
	}

	/**
	 * Devuelve el consumo total de los viajes asignados a este vehiculo.
	 */
	public double getConsumoTotalViajesAsignados() {
		double total = 0;
		for (Iterator viajesIt = this.getViajes().iterator(); viajesIt
				.hasNext();) {
			Viaje viaje = (Viaje) viajesIt.next();
			total += this.getConsumoCombustible(viaje);
		}
		return total;
	}

	public int getKmBuenos() {
		int total = 0;
		for (Viaje viaje : viajes) {
			total = total + (int) viaje.getKmBuenCamino();
		}
		return total;
	}

	public int getKmMalos() {

		int total = 0;
		for (Viaje viaje : viajes) {
			total = total + (int) viaje.getKmMalCamino();
		}
		return total;
	}
}
